<template>
  <div class="my-process-designer">
    <defs ref="customSuccessDefs">
      <marker id="sequenceflow-end-white-success" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10"
              markerHeight="10" orient="auto">
        <path class="success-arrow" d="M 1 5 L 11 10 L 1 15 Z"
              style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
      </marker>
      <marker id="conditional-flow-marker-white-success" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10"
              markerHeight="10" orient="auto">
        <path class="success-conditional" d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
              style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
      </marker>
    </defs>
    <!-- 自定义箭头样式，用于失败状态下流程连线箭头 -->
    <defs ref="customFailDefs">
      <marker id="sequenceflow-end-white-fail" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10"
              markerHeight="10" orient="auto">
        <path class="fail-arrow" d="M 1 5 L 11 10 L 1 15 Z"
              style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
      </marker>
      <marker id="conditional-flow-marker-white-fail" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10"
              markerHeight="10" orient="auto">
        <path class="fail-conditional" d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
              style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
      </marker>
    </defs>
    <div class="my-process-designer__container">
      <div class="my-process-designer__canvas" ref="bpmn-canvas"></div>
    </div>

  </div>
</template>

<script>
import {
  Cpu,
  Download,
  Edit,
  FolderOpened,
  Histogram,
  Refresh,
  RefreshLeft,
  RefreshRight,
  ScaleToOriginal,
  View,
  ZoomIn,
  ZoomOut
} from "@element-plus/icons-vue";
import BpmnViewer from "bpmn-js/lib/Viewer";
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
import DefaultEmptyXML from "components/start-activiti/designer/plugins/defaultEmpty";
// 翻译方法
import customTranslate from "components/start-activiti/designer/plugins/translate/customTranslate";
import translationsCN from "components/start-activiti/designer/plugins/translate/zh";
// 模拟流转流程
// 标签解析构建器
// 标签解析 Moddle
import camundaModdleDescriptor from "components/start-activiti/designer/plugins/descriptor/camundaDescriptor.json";
import activitiModdleDescriptor from "components/start-activiti/designer/plugins/descriptor/activitiDescriptor.json";
import flowableModdleDescriptor from "components/start-activiti/designer/plugins/descriptor/flowableDescriptor.json";
// 标签解析 Extension
import camundaModdleExtension from "components/start-activiti/designer/plugins/extension-moddle/camunda";
import activitiModdleExtension from "components/start-activiti/designer/plugins/extension-moddle/activiti";
import flowableModdleExtension from "components/start-activiti/designer/plugins/extension-moddle/flowable";

import Codemirror from "codemirror-editor-vue3";
import "codemirror/theme/monokai.css";
import "codemirror/mode/javascript/javascript.js";
import "codemirror/mode/xml/xml.js";
import ModelingModule from 'bpmn-js/lib/features/modeling';
import elementColorMapper from "@/views/start/activiti/components/bpmn/elementColorMapper";
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';

export default {
  name: "bpmnProcTrack",

  components: {
    Codemirror
  },
  setup() {
    return {
      Histogram,
      Cpu,
      Refresh,
      RefreshLeft,
      RefreshRight,
      Edit,
      ZoomOut, ZoomIn, View, Download, FolderOpened, ScaleToOriginal
    };
  },
  emits: ["destroy", "init-finished", "commandStack-changed", "update:modelValue", "change", "canvas-viewbox-changed", "element-click"],
  props: {
    modelValue: String, // xml 字符串
    elementsHistory: Array,
    translations: Object, // 自定义的翻译文件
    options: {
      type: Object,
      default: () => ({})
    }, // 自定义的翻译文件
    additionalModel: [Object, Array], // 自定义model
    moddleExtension: Object, // 自定义moddle
    onlyCustomizeAddi: {
      type: Boolean,
      default: false
    },
    onlyCustomizeModdle: {
      type: Boolean,
      default: false
    },
    simulation: {
      type: Boolean,
      default: true
    },
    keyboard: {
      type: Boolean,
      default: true
    },
    prefix: {
      type: String,
      default: "camunda"
    },
    events: {
      type: Array,
      default: () => ["element.click"]
    },
    headerButtonSize: {
      type: String,
      default: "mini",
      validator: value => ["default", "large", "small"].indexOf(value) !== -1
    },
    headerButtonType: {
      type: String,
      default: "primary",
      validator: value => ["default", "primary", "success", "warning", "danger", "info"].indexOf(value) !== -1
    }
  },
  data() {
    return {
      defaultZoom: 1,
      previewModelVisible: false,
      simulationStatus: false,
      previewResult: "",
      previewType: "xml",
      recoverable: false,
      revocable: false,
      cmOptions: {
        mode: "xml", // 语言模式
        theme: "monokai", // 主题
        lineNumbers: true, // 显示行号
        smartIndent: true, // 智能缩进
        readOnly: true,
        indentUnit: 2, // 智能缩进单位为4个空格长度
        foldGutter: true, // 启用行槽中的代码折叠
        styleActiveLine: true // 显示选中行的样式
      }
    };
  },
  computed: {
    additionalModules() {
      const Modules = [];
      // 翻译模块
      const TranslateModule = {
        translate: ["value", customTranslate(this.translations || translationsCN)]
      };
      Modules.push(TranslateModule);
      Modules.push(MoveCanvasModule);
      Modules.push(ModelingModule)
      Modules.push(ZoomScrollModule)

      if (this.prefix === "camunda") {
        Modules.push(camundaModdleExtension);
      }
      if (this.prefix === "flowable") {
        Modules.push(flowableModdleExtension);
      }
      if (this.prefix === "activiti") {
        Modules.push(activitiModdleExtension);
      }

      return Modules;
    },
    moddleExtensions() {
      const Extensions = {};
      // 仅使用用户自定义模块
      if (this.onlyCustomizeModdle) {
        return this.moddleExtension || null;
      }

      // 插入用户自定义模块
      if (this.moddleExtension) {
        for (let key in this.moddleExtension) {
          Extensions[key] = this.moddleExtension[key];
        }
      }

      // 根据需要的 "流程类型" 设置 对应的解析文件
      if (this.prefix === "activiti") {
        Extensions.activiti = activitiModdleDescriptor;
      }
      if (this.prefix === "flowable") {
        Extensions.flowable = flowableModdleDescriptor;
      }
      if (this.prefix === "camunda") {
        Extensions.camunda = camundaModdleDescriptor;
      }

      return Extensions;
    }
  },
  mounted() {

    this.initBpmnModeler();
    this.createNewDiagram(this.modelValue);

  },
  beforeUnmount() {
    if (this.bpmnModeler) this.bpmnModeler.destroy();
    this.$emit("destroy", this.bpmnModeler);
    this.bpmnModeler = null;
  },
  methods: {
    initBpmnModeler() {
      if (this.bpmnModeler) return;
      this.bpmnModeler = new BpmnViewer({
        container: this.$refs["bpmn-canvas"],
        keyboard: this.keyboard ? {bindTo: document} : null,
        additionalModules: this.additionalModules,
        // moddleExtensions: this.moddleExtensions,
        ...this.options
      });


      this.$emit("init-finished", this.bpmnModeler);
      this.initModelListeners();
    },
    initModelListeners() {
      const EventBus = this.bpmnModeler.get("eventBus");
      const that = this;
      // 注册需要的监听事件, 将. 替换为 - , 避免解析异常
      this.events.forEach(event => {
        EventBus.on(event, function (eventObj) {
          let eventName = event.replace(/\./g, "-");
          let element = eventObj ? eventObj.element : null;
          that.$emit(eventName, element, eventObj);
        });
      });
      // 监听图形改变返回xml
      EventBus.on("commandStack.changed", async event => {
        try {
          this.recoverable = this.bpmnModeler.get("commandStack").canRedo();
          this.revocable = this.bpmnModeler.get("commandStack").canUndo();
          let {xml} = await this.bpmnModeler.saveXML({format: true});
          this.$emit("commandStack-changed", event);
          this.$emit("update:modelValue", xml);
          this.$emit("change", xml);
        } catch (e) {
          console.error(`[Process Designer Warn]: ${e.message || e}`);
        }
      });
      // 监听视图缩放变化
      this.bpmnModeler.on("canvas.viewbox.changed", ({viewbox}) => {
        this.$emit("canvas-viewbox-changed", {viewbox});
        const {scale} = viewbox;
        this.defaultZoom = Math.floor(scale * 100) / 100;
      });
    },
    /* 创建新的流程图 */
    async createNewDiagram(xml) {
      // 将字符串转换成图显示出来
      let newId = this.processId || `Process_${new Date().getTime()}`;
      let newName = this.processName || `业务流程_${new Date().getTime()}`;
      let xmlString = xml || DefaultEmptyXML(newId, newName, this.prefix);
      try {
        let {warnings} = await this.bpmnModeler.importXML(xmlString);
        const canvas = this.bpmnModeler.get('canvas');
        // console.log("加载模型完成")
        canvas.zoom('fit-viewport', 'auto'); //画布自适应居中
        canvas.zoom(0.8); //缩写至0.8倍

        // const svg = canvas._svg;
        // const customSuccessDefs = this.$refs.customSuccessDefs;
        // const customFailDefs = this.$refs.customFailDefs;
        // svg.appendChild(customSuccessDefs);
        // svg.appendChild(customFailDefs);
      } catch (e) {
        console.error(`[Process Designer Warn]: ${e?.message || e}`);
      }
      this.initElementHistoryColor()
    },


    initElementHistoryColor() {
      if (!this.elementsHistory) return;
      const modeling = this.bpmnModeler.get('modeling')
      const elementRegistry = this.bpmnModeler.get('elementRegistry');
      for (let i = 0; i < this.elementsHistory.length; i++) {
        let elementHistory = this.elementsHistory[i];
        const element = elementRegistry.get(elementHistory.defKey);
        if (!element) {
          return;
        }
        switch (element.type) {
          case 'bpmn:SequenceFlow': // 自定义连线颜色
            modeling.setColor(element, elementColorMapper.line[elementHistory.status]);
            break;
          default:
            modeling.setColor(element, elementColorMapper.element[elementHistory.status]);

            break;
        }
      }
    },


  }
};
</script>

<style>
.my-process-designer {
  height: 100%;
}

.my-process-designer__container {
  height: 100%;
}

.my-process-designer__canvas {
  height: 100%;
}

.djs-minimap {
  top: unset !important;
  right: 0 !important;
  bottom: 0 !important;
}

.drop-not-ok, .connect-not-ok, .drop-not-ok *, .connect-not-ok * {
  cursor: unset !important;
}


</style>
